ORG 0x10000
jmp loader_start
%include "fatCommon.inc"
BaseOfKernelFile	equ	0x00
OffsetOfKernelFile	equ	0x100000

BaseTmpOfKernelAddr	equ	0x00
OffsetTmpOfKernelFile	equ	0x7e00
MemoryStructBufferAddr	equ	0x7E00
;global printRichText
global SearchFile
global IsFileExistInSec
[SECTION gdt]
LABEL_GDT:		dd	0,0
LABEL_DESC_CODE32:	dd	0x0000FFFF,0x00CF9A00
LABEL_DESC_DATA32:	dd	0x0000FFFF,0x00CF9200

GdtLen	equ	$ - LABEL_GDT
GdtPtr	dw	GdtLen - 1
	dd	LABEL_GDT
SelectorCode32	equ	LABEL_DESC_CODE32 - LABEL_GDT
SelectorData32	equ	LABEL_DESC_DATA32 - LABEL_GDT

[SECTION gdt64]

LABEL_GDT64:		dq	0x0000000000000000
LABEL_DESC_CODE64:	dq	0x0020980000000000
LABEL_DESC_DATA64:	dq	0x0000920000000000

GdtLen64	equ	$ - LABEL_GDT64
GdtPtr64	dw	GdtLen64 - 1
		dd	LABEL_GDT64

SelectorCode64	equ	LABEL_DESC_CODE64 - LABEL_GDT64
SelectorData64	equ	LABEL_DESC_DATA64 - LABEL_GDT64

[SECTION .s16]
[BITS 16]
loader_start:
    ;初始化
    mov ax,cs
    mov ds,ax
    mov es,ax
    mov ax,0
    mov ss,ax
    mov sp,0x7c00
	
	push 0x000f
	push 2
	push tail
	call printRichText

	push 0x000f
	push 0x000c
	push StartLoaderMessage
	call printRichText

	push 0x000f
	push 2
	push tail
	call printRichText

    ;开启A20
    push ax
    in al,92h
    or al,00000010b
    out 92h,al
    pop ax
    ;关闭外部中断
    cli
    ;在16位模式下执行32位指令，要在指令前加上0x66
    db  0x66
    lgdt [GdtPtr]

    ;db  0x66
    ;lidt [IDT_POINTER]

    ;-----开启保护模式--------
    mov eax,cr0
    or eax,1
    mov cr0,eax
    ;-----开启保护模式结束-------

    mov ax,SelectorData32
    mov fs,ax
    ;------------------从mov fs,ax以后，fs的寻址能力就达到了32位
    mov eax,cr0
    and al,11111110b
    mov cr0,eax
    ;------打开外部中断----------
    sti
    ;------打开外部中断结束---------- 
    xor ah,ah
    xor dl,dl
    int 13h    
    mov ax,KernelFileName
	push ax
	call SearchFile  
	cmp ax,0
	je FileNotFound

	push OffsetTmpOfKernelFile
	push BaseTmpOfKernelAddr
	push ax
	call DownloadFile
	;这几句要在32位模式下写
	;db  0x66
	;jmp BaseOfKernelFile:OffsetOfKernelFile
	call KillMotor;关闭软盘驱动器(因为核心工作已经完成，接下来不需要软盘了)

	call CheckMemoryAddress
	cmp ax,0
	je program_exit

	call CheckSVGSupport
	cmp ax,0
	je program_exit

	call GetSVGAModeInfo
	cmp ax,0
	je program_exit
	;xchg bx,bx
	jmp InitGDTAndIDT

program_exit:
	jmp $

InitGDTAndIDT:
	cli			;======close interrupt
	;加载gdt表
	db	0x66
	lgdt	[GdtPtr]
	;加载idt表	
	;db	0x66
	;lidt	[IDT_POINTER]
	;设置pe标志位，开启保护模式
	mov	eax,	cr0
	or	eax,	1
	mov	cr0,	eax	

	jmp	dword SelectorCode32:GO_TO_TMP_Protect	


FileNotFound:
	;mov si,NoKernelMessage
	;mov bl,0x03
	;call print
	push 0x008C
	push 0x0015
	push NoKernelMessage
	call printRichText

	push 0x000f
	push 2
	push tail
	call printRichText

	jmp $
;------搜索文件 如果搜索成功，首簇号返回到ax中-----
SearchFile:
	push bp
	mov bp,sp
	sub sp,20h	
	push bx
	push cx


	;1=扇区号 2=读入的扇区数 3=驱动器号 4=es 5=bx
	mov cx,RootDirSectors
	mov bx,SectorNumOfRootDirStart

SearchFile_loop:	
	cmp cx,0
	je SearchFile_fail
	;先存储扇区号
	push bx

	push 8000h
	push 0
	push 0
	push 1
	push bx		
	call readSector

	;根据es:bx查询具体的文件信息
	;第1参数 扇区数量 第2参数 地址 第三参数 es 第四参数bx 
	push bx
	push es
	push word[bp + 4]
	push 1
	call IsFileExistInSec
	;返回的首簇号放在ax里面
	cmp ax,0;ax!=0表示在某个扇区找到首簇号了
	jne SearchFile_Success
	pop bx
	inc bx
	dec cx
	jmp SearchFile_loop

SearchFile_Success:
	pop bx
	jmp SearchFile_ret
	
SearchFile_fail:
	mov ax,0
	jmp SearchFile_ret

SearchFile_ret:
	pop cx
	pop bx

	leave

	ret 2
;------搜索文件结束 -------------------------------

;----------判断文件是否存在----------------------------
IsFileExistInSec:
	push bp
	mov bp,sp
	sub sp,20h	

	push cx
	push dx
	push si

	mov ax,[bp + 4]
	mov bl,al
	mov al,10h
	mul bl
	mov cx,ax
	mov ax,[bp + 6]
	mov si,ax
	mov ax,[bp + 8]
	mov es,ax
	mov bx,[bp + 10]

.next_diritem:
	push bx
	push si
	cmp cx,0
	je .file_not_found
	dec cx
.file_cmp_start:	
	mov dx,11
.file_cmp_go_on:
	cmp dx,0
	;如果所有字符都比较完了，那就是找到了
	je .file_found
	dec dx
	mov al,es:[bx]

	mov ah,[si]
	inc bx
	inc si
	cmp al,ah
	je .file_cmp_go_on
	;否则就是不相等，需要执行下一段逻辑
	pop si
	pop bx
	;这个地方加这一条修正es:bx指针,bx指针在比较完一个之后要移动到下一条，一个item的长度为32
	add bx,32
	jmp .next_diritem

.file_not_found:
	;不管结果是否找到，都要将压的si,bx进行恢复
	pop si
	pop bx	
	mov ax,0
	jmp .IsFileExistInSec_end
.file_found:
	;这个地方直接给簇号，否则就为0，
	;0不可能是簇号，因为数据区的簇号一定是需要大于1
	pop si
	pop bx	
	mov ax,es:[bx+26]	

.IsFileExistInSec_end:
	pop si
	pop dx
	pop cx

	leave

	ret 8
;----------------判断文件存在结束------------------

;1=首簇号 2=下载文件到内存的段地址 3=下载文件到内存的偏移地址
DownloadFile:
	push bp
	mov bp,sp
	sub sp,20h	

	;push cx

	;先读取首簇号的内容
	;然后查看fat表，看看是否结束
	;如果结束，则读取完毕
	;跳到相应代码的地方

	;以上逻辑的根本意思就是将簇号转成扇区号
	;以簇号2为例 前面有19+14个(数据区起始簇号为2)，所以，扇区号-簇号其偏移=31
	;1=扇区号 2=读入的扇区数 3=驱动器号 4=es 5=bx
	;mov ax,OffsetOfLoader
	;mov [bp - 6],ax

label_go_on_download:
	push word[bp + 8]
	push word[bp + 6]
	push 0
	push 1
	mov cx,[bp+4]
	add cx,SectorBalance
	add cx,RootDirSectors		
	push cx
	call readSector
	;将数据从临时地址移动到真正的地址

	push cx
	push eax
	push fs
	push edi
	push ds
	push esi
	;xchg bx,bx
	mov cx,200h
	mov ax,BaseOfKernelFile
	mov fs,ax
	;这个地方有bug,一般取数据，默认走ds,但是这里ds为原始ds
	mov bx,ds;先将bs保存一下
	mov edi,dword[OffsetOfKernelFileCount]

	mov ax,word[bp + 6]
	mov ds,ax
	mov eax,0
	mov ax,word[bp + 8]
	mov esi,eax

label_move_kernel:
	mov al,[ds:esi]
	mov [fs:edi],al
	inc esi
	inc edi
	loop label_move_kernel	
	;与329行对应，这里的ds被改变了
	mov ds,bx
	mov	dword	[OffsetOfKernelFileCount],	edi
	
	pop esi
	pop ds
	pop edi
	pop fs
	pop eax
	pop cx

	;重新获取首簇号
	push word[bp + 4]
	call GetFatEntry
	cmp	ax,	0fffh
	;如果读取fat表发现是0fffh，表示该文件已结束
	jz	Label_File_Loaded_OK
	;将新的首簇号继续存放在bp+4 也就是第一参数的位置，当然，这个实际上是改变了入参，有点影响
	mov [bp + 4],ax	
	mov ax,[bp + 8]
	add ax,[BPB_BytesPerSec]
	mov [bp + 8],ax
	jmp label_go_on_download


Label_File_Loaded_OK:
	;pop cx

	leave
	;之前写错了,这个要做一个内平衡
	ret 6

;1=首簇号 返回值 ax 如果ax = 0fffh,表示文件结束，否则就表示下一个簇号 
GetFatEntry:
	push bp
	mov bp,sp
	sub sp,20h	
	push bx
	push dx
	mov ax,[bp + 4]
	mov word[bp - 4],0;奇偶标志，如果值为1表示奇数	
	mov bx,3
	mul bx
	mov bx,2
	div bx
	cmp dx,0
	jz .label_even
	mov word[bp - 4],1;//存储奇偶标志
.label_even:
	xor dx,dx
	mov bx,[BPB_BytesPerSec]
	div bx
	add ax,SectorNumOfFAT1Start
	mov [bp-6],ax;存储扇区号
	mov [bp-8],dx;存储字节偏移
	push 8000h
	push 0
	push 0
	push 2
	push word[bp-6]
	call readSector
	;mov ax,[bp - 6]
	add bx,[bp-8]
	mov ax,es:[bx]
	mov bx,[bp - 4];//现在bx=奇偶标志
	cmp bx,1
	jnz label_event2
	;奇数就向右偏移4位
	shr ax,4

label_event2:
	and ax,0fffh

	pop dx
	pop bx

	leave

	ret	2

;1=扇区号 2=读入的扇区数 3=驱动器号 4=es 5=bx
readSector:
	push bp
	mov bp,sp
	sub sp,20h
	;push bx
	push cx
Label_Go_On_Reading:
	mov ax,[bp + 4]
	mov bl,[BPB_SecPerTrk]
	div bl
	inc ah
	mov cl,ah
	mov dh,al
	and dh,1
	shr al,1
	mov ch,al
	mov ax,[bp + 8]
	mov dl,al
	mov ax,[bp +10]
	mov es,ax
	;mov ax,[bp + 6]		
	mov bx,[bp + 12]
	mov ah,02h
	mov al,[bp+6]
	int 13h
	jc 	Label_Go_On_Reading
	pop cx
	;pop bx
	
	leave
	ret 0Ah

print:
	mov ah,0x0e
	mov bh,0
	mov bl,84h
.loop:
	mov al,[si]
	cmp al,0
	jz .print_done
	int 10h

	inc si
	jmp .loop
.print_done:
	ret	

;0=字符串 1=字符串长度 2=字符串属性((bit0-3)前景色+(bit4-6)背景色+(bit7)是否闪烁)
printRichText:
	push bp
	mov bp,sp
	sub sp,20h

	push ax
	push bx
	push cx
	push dx
	push es
;先获取当前行和列
	mov bh,0
	mov ah,03
	int 10h
	;这样就获取到了dh和dl
	mov ax,1301h
	mov bh,0
	mov bl,byte[bp + 8]
	mov cx,word[bp+6]
	push ax
	mov ax,ds
	mov es,ax
	pop ax
	;因为要用到bp，先将bp存起来
	push bp
	mov bp,word[bp + 4]
	int 10h

	pop bp
	pop es
	pop dx
	pop cx
	pop bx
	pop ax

	leave
	ret 06h

KillMotor:
	;具体细节见书本61-62页
	push bp
	mov bp,sp
	sub sp,20h

	push	dx
	mov	dx,	03F2h
	mov	al,	0	
	out	dx,	al
	pop	dx

	leave
	ret

CheckMemoryAddress:
	push bp
	mov bp,sp
	sub sp,20h
	push di
	push edx
	push ebx


	push 0x000f
	push StartGetMemStructMessage_Len
	push StartGetMemStructMessage
	call printRichText

	push 0x000f
	push 2
	push tail
	call printRichText

	mov	ax,	0x00
	mov	es,	ax
	mov	di,	MemoryStructBufferAddr	
	mov edx,0x534d4150 ;edx置特定标志
	xor ebx,ebx;先将ebx置为0
Label_Get_Mem_Struct:	
	mov eax,0xe820
	mov ecx,20
	int 0x15
	jc GetMemoryFail
	add	di,	20
	cmp	ebx,	0
	jne	Label_Get_Mem_Struct
	jmp GetMemoryOK

GetMemoryFail:
	push 0x008C
	push GetMemStructErrMessage_Len
	push GetMemStructErrMessage
	call printRichText

	push 0x000f
	push 2
	push tail
	call printRichText
	mov ax,0
GetMemoryOK:
	push 0x000f
	push GetMemStructOKMessage_Len
	push GetMemStructOKMessage
	call printRichText

	push 0x000f
	push 2
	push tail
	call printRichText
	mov ax,1
CheckMemoryAddress_end:
	pop 	ebx
	pop 	edx
	pop 	di
	leave
	ret

CheckSVGSupport:
	push bp
	mov bp,sp
	sub sp,20h

	push 0x000f
	push StartGetSVGAVBEInfoMessage_Len
	push StartGetSVGAVBEInfoMessage
	call printRichText

	push 0x000f
	push 2
	push tail
	call printRichText

	mov	ax,	0x00
	mov	es,	ax
	mov	di,	0x8000
	mov	ax,	4F00h;ah一定要是4f,ah是功能号 00-06
	;https://invisprints.wordpress.com/2016/09/22/svga-bian-cheng-mi-ji-bi-ji/ 怕自己记不清
	push bp;这里检查bios是否支持vbe,但是似乎会改变bp的位置，所以，先保存一下bp
	int	10h
	pop bp
	cmp	ax,	004Fh
	jz 	CheckSVGSSuccess

CheckSVGSFailed:
	push 0x008c
	push GetSVGAVBEInfoErrMessage_Len
	push GetSVGAVBEInfoErrMessage
	call printRichText

	push 0x000f
	push 2
	push tail
	call printRichText
	mov ax,0
	jmp GetSVG_end
CheckSVGSSuccess:
	push 0x000f
	push GetSVGAVBEInfoOKMessage_Len
	push GetSVGAVBEInfoOKMessage
	call printRichText

	push 0x000f
	push 2
	push tail
	call printRichText
	mov ax,1	
	jmp GetSVG_end
GetSVG_end:
	mov sp,bp
	pop bp
	ret	

GetSVGAModeInfo:
	push bp
	mov bp,sp
	sub sp,20h

	push 0x000f
	push StartGetSVGAModeInfoMessage_Len
	push StartGetSVGAModeInfoMessage
	call printRichText

	push 0x000f
	push 2
	push tail
	call printRichText
	;这段逻辑的详细意义见vbe知识.txt
	mov	ax,	0x00
	mov	es,	ax
	mov	si,	0x800e
	mov	esi,	dword	[es:si]
	mov	edi,	0x8200
Label_SVGA_Mode_Info_Get:
	mov	cx,	word	[es:esi]

;=======	display SVGA mode information
	push	ax
	
	mov	ax,	00h
	mov	al,	ch
	call	Label_DispAL

	mov	ax,	00h
	mov	al,	cl	
	call	Label_DispAL
	
	pop	ax
	;在每个地方加个换行
	push 0x000f
	push 2
	push tail
	call printRichText

;=======
	cmp	cx,	0FFFFh
	jz	Label_SVGA_Mode_Info_Finish

	push bp
	mov	ax,	4F01h
	int	10h
	pop bp
	;xchg bx,bx
	cmp	ax,	004Fh

	jnz	Label_SVGA_Mode_Info_FAIL	

	add	esi,	2
	add	edi,	0x100

	jmp	Label_SVGA_Mode_Info_Get
Label_SVGA_Mode_Info_FAIL:

	push 0x008C
	push GetSVGAModeInfoErrMessage_Len
	push GetSVGAModeInfoErrMessage
	call printRichText

	push 0x000f
	push 2
	push tail
	call printRichText

Label_SET_SVGA_Mode_VESA_VBE_FAIL:
	mov ax,0
	jmp	GetSVGAModeInfo_end

Label_SVGA_Mode_Info_Finish:
	push 0x000f
	push 2
	push tail
	call printRichText

	push 0x000f
	push GetSVGAModeInfoOKMessage_Len
	push GetSVGAModeInfoOKMessage
	call printRichText

	push 0x000f
	push 2
	push tail
	call printRichText

;=======	set the SVGA mode(VESA VBE)
	push bp
	;0100 0101 010D 010E 010F 0110 0111 0112 0140 0141 0142 0146
	mov	ax,	4F02h
	mov	bx,	4143h	;========================mode : 0x180 or 0x143
	int 	10h
	pop bp
	cmp	ax,	004Fh
	jnz	Label_SET_SVGA_Mode_VESA_VBE_FAIL
	mov ax,1

GetSVGAModeInfo_end:
	leave
	ret


[SECTION .s32]
[BITS 32]
GO_TO_TMP_Protect:
;=======	go to tmp long mode
	mov	ax,	0x10
	mov	ds,	ax
	mov	es,	ax
	mov	fs,	ax
	mov	ss,	ax
	mov	esp,	7E00h
	call support_long_mode
	test	eax,	eax;检查eax是否为0，若为0，则ZF=1
	je no_support

;=======	初始化页表？这部分有点不太明白，抄的，希望在后面的章节能看到详细说明

	mov	dword	[0x90000],	0x91007
	mov	dword	[0x90800],	0x91007		

	mov	dword	[0x91000],	0x92007

	mov	dword	[0x92000],	0x000083

	mov	dword	[0x92008],	0x200083

	mov	dword	[0x92010],	0x400083

	mov	dword	[0x92018],	0x600083

	mov	dword	[0x92020],	0x800083

	mov	dword	[0x92028],	0xa00083

;=======	加载64位GDT表

	db	0x66
	lgdt	[GdtPtr64]
	mov	ax,	0x10
	mov	ds,	ax
	mov	es,	ax
	mov	fs,	ax
	mov	gs,	ax
	mov	ss,	ax

	mov	esp,	7E00h

;=======    开启PAE
	mov eax,cr4
	bts eax,5 ;设置cr4寄存器的第5位去开启
	mov cr4,eax

;=======	将页表地址传给cr3
	mov	eax,	0x90000
	mov	cr3,	eax	

;=======	激活IA-32e模式

	mov ecx,0xc0000080
	rdmsr
	bts eax,8;将第8位设置为1
	wrmsr

;======= 开启pe标志，激活分页机制
	mov	eax,	cr0
	bts	eax,	0
	bts	eax,	31
	mov	cr0,	eax
	xchg bx,bx
	jmp	SelectorCode64:OffsetOfKernelFile

support_long_mode:
	;80000000h功能号用来检测cpu的最大扩展功能号，若小于80000001h,则必不支持64位长模式
	mov eax,0x80000000
	cpuid
	cmp eax,0x80000001
	setnb al ;若eax小于80000001h,发生借位，cf位被置1，则令al为0
	jb	support_long_mode_done
	mov eax,0x80000001
	cpuid
	bt edx,29 ;检测edx 第29位--并将该位的值传送至CF 第29位代表是否支持64位
	setc al ;将进位或借位标志位的值赋值给al
support_long_mode_done:
	movzx eax,al;将8位al移动到eax中，高位赋0
	ret

no_support:
	jmp $	

[SECTION .s16_1]
[BITS 16]
Label_DispAL:
	push	ecx
	push	edx
	push	edi
    push ax

    ;获取行和列，并计算下一个字符的位置
    mov bh,0
	mov ah,03
	int 10h
    mov al,dh
    mov bl,80
    mul bl
    mov dh,0
    add ax,dx
    mov bx,2
    mul bx
    mov [DisplayPosition],ax

	mov ax, 0xb800
	mov gs,ax
    pop ax

	mov	edi,	[DisplayPosition]
	mov	ah,	0Fh
	mov	dl,	al
	shr	al,	4
	mov	ecx,	2
.begin:

	and	al,	0Fh
	cmp	al,	9
	ja	.1
	add	al,	'0'
	jmp	.2
.1:

	sub	al,	0Ah
	add	al,	'A'
.2:

	mov	[gs:edi],	ax
	add	edi,	2
	
	mov	al,	dl
	loop	.begin

    mov eax,edi
    shr ax,1
    mov bl,80
    div bl
    mov dh,al
    mov dl,ah

    mov ah, 02h       ; 功能号02h，用于设置光标位置
    mov bh, 0         ; 页号（对于文本模式总是0）
    ;mov dh, 行号    ; 行号（从0开始）
    ;mov dl, 列号    ; 列号（从0开始）
    int 10h          ; 调用中断

	;mov	[DisplayPosition],	edi

	pop	edi
	pop	edx
	pop	ecx
	
	ret		
IDT:
	times	0x50	dq	0
IDT_END:

IDT_POINTER:
		dw	IDT_END - IDT - 1
		dd	IDT
OffsetOfKernelFileCount	dd	OffsetOfKernelFile
StartGetMemStructMessage	db	"Start Get Memory Struct."
StartGetMemStructMessage_Len	equ	$ - StartGetMemStructMessage
GetMemStructErrMessage	db	"Get Memory Struct ERROR"
GetMemStructErrMessage_Len 		equ $ - GetMemStructErrMessage
GetMemStructOKMessage	db	"Get Memory Struct SUCCESSFUL!"
GetMemStructOKMessage_Len 		equ $ - GetMemStructOKMessage
KernelFileName:		db	"KERNEL  BIN",0
NoKernelMessage:	db	"ERROR:No Kernel Found",0
StartLoaderMessage:     db      "Start Loader"
tail:					db 13,10
StartGetSVGAVBEInfoMessage	db	"Start Get SVGA VBE Info"
StartGetSVGAVBEInfoMessage_Len 		equ $ - StartGetSVGAVBEInfoMessage
GetSVGAVBEInfoErrMessage	db	"Get SVGA VBE Info ERROR"
GetSVGAVBEInfoErrMessage_Len	equ $ - GetSVGAVBEInfoErrMessage
GetSVGAVBEInfoOKMessage		db	"Get SVGA VBE Info SUCCESSFUL!"
GetSVGAVBEInfoOKMessage_Len		equ $ - GetSVGAVBEInfoOKMessage
StartGetSVGAModeInfoMessage	db	"Start Get SVGA Mode Info"
StartGetSVGAModeInfoMessage_Len	equ $ - StartGetSVGAModeInfoMessage
GetSVGAModeInfoErrMessage	db	"Get SVGA Mode Info ERROR"
GetSVGAModeInfoErrMessage_Len	equ $ - GetSVGAModeInfoErrMessage
GetSVGAModeInfoOKMessage	db	"Get SVGA Mode Info SUCCESSFUL!"
GetSVGAModeInfoOKMessage_Len	equ $ - GetSVGAModeInfoOKMessage
DisplayPosition		dd	0